﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Core
{
    public interface IAssetCounterHelper : IHelperTag
    {
        AssetBundle GetAssetBundle(string bundlePath);
        ETTask<AssetBundle> GetAssetBundleAsync(string bundlePath);
        void SetLoadingTask(string bundlePath, ETTask<AssetBundle> task);
        void SetBundle(string bundlePath, AssetBundle bundle);
        void AddAssetCount(string assetPath, string bundlePath);
        void ReduceAssetCount(string assetPath, int count);
        void AddBundleDependencies(string bundlePath, string[] dependencies);
        void TryUnloadUnusedBundles();
    }

    public class AssetCounterHelper : Helper, IAssetCounterHelper
    {
        public Dictionary<string, AssetData> assetsCounter = new Dictionary<string, AssetData>();
        public Dictionary<string, BundleData> bundlesCounter = new Dictionary<string, BundleData>();

        public bool isNeedCheckUnusedBundle;
        private IAssetHelper assetHelper;

        public override void OnInit()
        {
            assetHelper = GetHelper<IAssetHelper>();
        }

        public void AddAssetCount(string assetPath, string bundlePath)
        {
            if (!assetsCounter.TryGetValue(assetPath, out var assetData))
            {
                if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
                {
                    bundleData = new BundleData();
                    bundlesCounter[bundlePath] = bundleData;
                }

                assetData = new AssetData();
                assetData.bundleData = bundleData;
                assetsCounter[assetPath] = assetData;

                if (bundleData.usingDatas == null)
                    bundleData.usingDatas = new Dictionary<string, UsingData>();

                bundleData.usingDatas[assetPath] = assetData;
            }

            assetData.usingCount++;
        }

        public void ReduceAssetCount(string assetPath, int count)
        {
            if (!assetsCounter.TryGetValue(assetPath, out var assetData))
                return;
            assetData.usingCount -= count;

            if (assetData.usingCount == 0)
                TryUnloadUnusedBundles();

            if (assetData.usingCount < 0)
            {
                assetData.usingCount = 0;
                Debug.LogError($"重复减少资源计数：{assetPath}，计数异常");
            }
        }

        public void SetBundle(string bundlePath, AssetBundle bundle)
        {
            if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
            {
                Log.Info($"资源包在加载完成前，加载资源对象已经移除相关资源引用，卸载此资源包:{bundlePath}");
                assetHelper.UnloadAssetBundle(bundle);
                return;
            }

            bundleData.SetBundle(bundle);
        }

        public void SetLoadingTask(string bundlePath, ETTask<AssetBundle> task)
        {
            if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
                return;

            bundleData.loadingTask = task;
        }

        public async ETTask<AssetBundle> GetAssetBundleAsync(string bundlePath)
        {
            if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
                return null;

            if (bundleData.bundle != null)
                return bundleData.bundle;

            if (bundleData.loadingTask != null)
                return await bundleData.loadingTask.WaitAppend();

            return null;
        }

        public AssetBundle GetAssetBundle(string bundlePath)
        {
            if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
                return null;

            return bundleData.bundle;
        }

        public void TryUnloadUnusedBundles()
        {
            #region 移除资源包

            var unloadBundles = new List<string>();

            foreach (var bundlePair in bundlesCounter)
            {
                var bundleData = bundlePair.Value;
                var bundlePath = bundlePair.Key;

                var count = bundleData.GetAllUsingCount();
                if (count == 0)
                {
                    if (bundleData.bundle != null)
                        assetHelper.UnloadAssetBundle(bundleData.bundle);
                    unloadBundles.Add(bundlePath);
                    bundleData.usingDatas?.Clear();

                    // 移除依赖对自己的引用
                    if (bundleData.dependencies != null)
                    {
                        foreach (var dependencyData in bundleData.dependencies.Values)
                            dependencyData.usingDatas?.Remove(bundlePath);
                    }
                }
            }

            foreach (var unloadBundle in unloadBundles)
            {
                if (bundlesCounter.TryGetValue(unloadBundle, out var bundleData))
                    bundleData.Dispose();
                bundlesCounter.Remove(unloadBundle);
            }

            unloadBundles.Clear();

            #endregion

            #region 移除资源

            var removeAssets = new List<string>();

            foreach (var assetPair in assetsCounter)
            {
                if (assetPair.Value.usingCount == 0)
                {
                    removeAssets.Add(assetPair.Key);
                }
            }

            foreach (var removePath in removeAssets)
            {
                assetsCounter.Remove(removePath);
            }

            removeAssets.Clear();

            #endregion
        }

        public void AddBundleDependencies(string bundlePath, string[] dependencies)
        {
            if (!bundlesCounter.TryGetValue(bundlePath, out var bundleData))
            {
                bundleData = new BundleData();
                bundlesCounter[bundlePath] = bundleData;
            }

            if (bundleData.dependencies == null)
                bundleData.dependencies = new Dictionary<string, BundleData>();

            foreach (var dependency in dependencies)
            {
                if (!bundlesCounter.TryGetValue(dependency, out var dependencyBundleData))
                {
                    dependencyBundleData = new BundleData();
                    bundlesCounter[dependency] = dependencyBundleData;
                }

                bundleData.dependencies[dependency] = dependencyBundleData;

                if (dependencyBundleData.usingDatas == null)
                    dependencyBundleData.usingDatas = new Dictionary<string, UsingData>();

                dependencyBundleData.usingDatas[bundlePath] = bundleData;
            }
        }

        public void DebugCounterInfo()
        {
            var builder = new StringBuilder();

            builder.AppendLine("[asset info]");
            foreach (var item in assetsCounter)
            {
                builder.AppendLine($"{item.Key}:{item.Value.GetAllUsingCount()}");
            }

            builder.AppendLine();
            builder.AppendLine("[bundle info]");

            foreach (var item in bundlesCounter)
            {
                builder.Append($"{item.Key}:{item.Value.GetAllUsingCount()}");

                if (item.Value.usingDatas != null)
                {
                    builder.Append(" | usingData:");
                    foreach (var usingData in item.Value.usingDatas)
                    {
                        builder.Append($" {usingData.Key}, ");
                    }
                }

                if (item.Value.dependencies != null)
                {
                    builder.Append(" | dependencies:");
                    foreach (var depen in item.Value.dependencies)
                    {
                        builder.Append($" {depen.Key}, ");
                    }
                }

                builder.AppendLine();
            }

            Debug.Log(builder.ToString());
        }
    }

    public class UsingData
    {
        public int usingCount;
        // 资源包被哪些引用依赖（包含资源文件和资源包）
        public Dictionary<string, UsingData> usingDatas;

        public int GetAllUsingCount()
        {
            var count = usingCount;

            if (usingDatas != null)
                foreach (var usingData in usingDatas.Values)
                    count += usingData.GetAllUsingCount();

            return count;
        }
    }

    public class AssetData : UsingData
    {
        public BundleData bundleData;
    }

    public class BundleData : UsingData, IDisposable
    {
        public AssetBundle bundle;
        // 资源包依赖了哪些其他资源包
        public Dictionary<string, BundleData> dependencies;
        public ETTask<AssetBundle> loadingTask;

        public void SetBundle(AssetBundle assetBundle)
        {
            bundle = assetBundle;
            loadingTask = null;
        }

        public void Dispose()
        {
            bundle = null;
            dependencies = null;
            loadingTask = null;
        }
    }
}
